/*
 * webrtc_http_server.c - Source for webrtc_http_server test program
 *
 * Copyright (C) 2013 Kurento
 *   Contact: Jose Antonio Santos Cadenas <santoscadenas@kurento.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <gst/gst.h>
#include <libsoup/soup.h>
#include <string.h>

GST_DEBUG_CATEGORY_STATIC (webrtc_http_server);
#define GST_CAT_DEFAULT webrtc_http_server

#define SERVER "SERVER"
#define MESSAGE "message"

#define PORT 8180
#define BUFFER_LEN 200

#define WEBRTC "webrtc"
#define RTP "rtp"
#define ID "id"

#define CANDIDATE_PREFIX "a=candidate:"
#define USER_PREFIX "a=ice-ufrag:"
#define PWD_PREFIX "a=ice-pwd:"
#define CRYPTO_PREFIX "a=crypto:"

#define DEFAULT_CRYTPO_TYPE "AES_CM_128_HMAC_SHA1_80"

static GMainLoop *loop;
static GHashTable *cookies;
static GRand *rand;

static void
bus_msg(GstBus *bus, GstMessage *msg, gpointer pipe)
{
  gint64 *id;

  switch (msg->type) {
    case GST_MESSAGE_ERROR: {
      GST_ERROR_OBJECT(pipe, "Error: %P", msg);
      id = g_object_get_data(pipe, ID);
      if (id != NULL) {
        // This destroys the pipeline
        g_hash_table_remove(cookies, id);
      }
      break;
    }
    case GST_MESSAGE_WARNING: {
      GST_WARNING_OBJECT(pipe, "Warning: %P", msg);
      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe),
                                        GST_DEBUG_GRAPH_SHOW_ALL, "warning");
      break;
    }
    case GST_MESSAGE_STATE_CHANGED:
      break;
    case GST_MESSAGE_ELEMENT:
      GST_DEBUG_OBJECT(pipe, "Element: %P", msg);
      break;
    case GST_MESSAGE_STREAM_STATUS:
      GST_DEBUG_OBJECT(pipe, "Stream status: %P", msg);
      break;
    case GST_MESSAGE_NEW_CLOCK:
      break;
    default:
      GST_DEBUG_OBJECT(pipe, "Message: %P", msg);
      break;
  }
}


static void
local_candidates_gathered (GstElement * webrtc, GstStructure * candidates,
                           gpointer user_data)
{
  SoupMessage * msg = g_object_steal_data (G_OBJECT(webrtc), MESSAGE);
  SoupServer *server = g_object_steal_data (G_OBJECT (webrtc), SERVER);
  gchar *line, *key;
  GstStructure *credentials;
  gint i, len;

  if (msg == NULL || server == NULL)
    return;

  g_object_get(G_OBJECT(webrtc), "local-ice-credentials", &credentials, NULL);

  line = g_strdup_printf("ice_user = \"%s\";\n",
                         gst_structure_get_string(credentials, "user"));
  soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, line,
                            strlen(line));
  line = g_strdup_printf("ice_pwd = \"%s\";\n",
                         gst_structure_get_string(credentials, "passwd"));
  gst_structure_free(credentials);
  soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, line,
                            strlen(line));

  g_object_get(G_OBJECT(webrtc), "send-key", &key, NULL);
  line = g_strdup_printf("key = \"%s\";\n", key);
  g_free(key);
  soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, line,
                            strlen(line));

  line = "remoteCandidates = [\n";
  soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC, line,
                            strlen(line));

  len = gst_structure_n_fields(candidates);

  for (i = 0; i < len; i ++) {
    GstStructure * candidate;
    const gchar * name;

    name = gst_structure_nth_field_name(candidates, i);
    if (name != NULL) {
      if (gst_structure_get(candidates, name, GST_TYPE_STRUCTURE, &candidate, NULL)) {
        const gchar * sdp = gst_structure_get_string(candidate, "sdpline");
        if (sdp != NULL) {
          line = "\"";
          soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
                                    line, strlen(line));
          soup_message_body_append (msg->response_body, SOUP_MEMORY_COPY, sdp,
                                    strlen(sdp) - 1);
          line = "\", \n";
          soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
                                    line, strlen(line));
        }
        gst_structure_free(candidate);
      }
    }
  }

  line = "]\n";
  soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC, line,
                            strlen(line));

  line = "</script>\n</body>\n</html>\n";
  soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC, line,
                            strlen(line));

  soup_server_unpause_message (server, msg);

  g_object_unref(server);
  g_object_unref(msg);
}

static GstCaps*
rtpbin_request_pt_map(GstElement *ptdemux, guint session, guint pt, gpointer user_data) {
  GstElement *pipe = GST_ELEMENT(user_data);

  GST_DEBUG_OBJECT(pipe, "Request for pt: %d and session: %d\n", pt, session);

  if (pt == 100) {
    return gst_caps_new_simple("application/x-rtp",
                               "payload", G_TYPE_INT, pt,
                               "media", G_TYPE_STRING, "video",
                               "clock-rate", G_TYPE_INT, 90000,
                               "encoding-name", G_TYPE_STRING,
                                "VP8-DRAFT-IETF-01",
                               "rtcp-fb-x-gstreamer-fir-as-repair",
                                  G_TYPE_BOOLEAN, TRUE,
                               NULL);
  } else {
    return gst_caps_new_simple("application/x-rtp",
                               "payload", G_TYPE_INT, pt,
                               NULL);
  }
}


static gboolean
typefind_sink_event(GstPad *pad, GstObject *typefind, GstEvent *event)
{
  GstStructure *s;
  GstEvent *force_key_unit_event;
  GstPad *peer;

  switch (GST_EVENT_TYPE(event)) {
    case GST_EVENT_GAP:
      peer = GST_PAD_PEER(pad);
      if (peer != NULL) {
        GST_WARNING("Request key frame");
        s = gst_structure_new ("GstForceKeyUnit",
                              "all-headers", G_TYPE_BOOLEAN, TRUE,
                              NULL);
        force_key_unit_event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s);
        gst_pad_send_event(peer, force_key_unit_event);
      }
    default:
      return gst_pad_event_default(pad, typefind, event);
  }
}

static void
have_type (GstElement* typefind, guint prob, GstCaps* caps, GstElement *pipe)
{
  GST_DEBUG_OBJECT(pipe, "Found type. Prob: %d, caps: %P", prob, caps);

  if (prob == 100) {
    GstCaps *caps;
    GstPad *sink;
    GstElement *rate = gst_element_factory_make("videorate", NULL);
    GstElement *scale = gst_element_factory_make("videoscale", NULL);
    GstElement *overlay = gst_element_factory_make("clockoverlay", NULL);
    GstElement *capsfilter = gst_element_factory_make("capsfilter", NULL);
    GstElement *queue2 = gst_element_factory_make("queue", NULL);
    GstElement *enc = gst_element_factory_make("vp8enc", NULL);
    GstElement *pay = gst_element_factory_make("rtpvp8pay", NULL);

    GstElement *rtpbin = g_object_get_data(G_OBJECT(pipe), RTP);

    gint timeout = GPOINTER_TO_INT(g_object_steal_data(G_OBJECT(typefind),
                                                       "timeout"));
    g_source_remove(timeout);

    caps = gst_caps_new_simple("video/x-raw",
                                "framerate", GST_TYPE_FRACTION, 15, 1,
                                "width", G_TYPE_INT, 320,
                                "height", G_TYPE_INT, 240,
                                NULL);

    GST_DEBUG("Caps: %P", caps);

    g_object_set(capsfilter, "caps", caps, NULL);
    gst_caps_unref(caps);

    g_object_set(pay, "pt", 100, NULL);
    g_object_set(enc, "deadline", GST_SECOND / 30, "target-bitrate", 256000,
                 "keyframe-mode", 0, "end-usage", 2, NULL);
    g_object_set(scale, "add-borders", TRUE, NULL);

    g_object_set(overlay, "font-desc", "Sans 28", NULL);

    gst_element_set_state(scale, GST_STATE_PLAYING);
    gst_element_set_state(overlay, GST_STATE_PLAYING);
    gst_element_set_state(capsfilter, GST_STATE_PLAYING);
    gst_element_set_state(queue2, GST_STATE_PLAYING);
    gst_element_set_state(enc, GST_STATE_PLAYING);
    gst_element_set_state(pay, GST_STATE_PLAYING);

    gst_bin_add_many(GST_BIN(pipe), rate, scale, overlay, capsfilter, queue2, enc, pay, NULL);

    gst_element_link_many(typefind, rate, scale, overlay, capsfilter, queue2, enc, pay, NULL);

    gst_element_set_state(rate, GST_STATE_PLAYING);

    gst_element_link_pads(pay, "src", rtpbin, "send_rtp_sink_0");

    sink = gst_element_get_static_pad(typefind, "sink");
    if (sink != NULL) {
      gst_pad_set_event_function(sink, typefind_sink_event);
      g_object_unref(sink);
    }
  }
}

static gboolean
request_key_frame (GstElement *elem)
{
  GstStructure *s;
  GstEvent *force_key_unit_event;

  GST_WARNING("Request key frame");
  s = gst_structure_new ("GstForceKeyUnit",
                         "all-headers", G_TYPE_BOOLEAN, TRUE,
                         NULL);
  force_key_unit_event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s);
  gst_element_send_event(elem, force_key_unit_event);
  return TRUE;
}

static void
rtpbin_pad_added (GstElement *rtpbin, GstPad *pad, GstElement *pipe)
{
  if (gst_pad_get_direction(pad) != GST_PAD_SRC)
    return;

  GST_INFO_OBJECT(pipe, "rtp pad added: %s", GST_OBJECT_NAME(pad));

  if (g_strcmp0("send_rtcp_src_0", GST_OBJECT_NAME(pad)) == 0) {
    GstElement *webrtc = g_object_get_data(G_OBJECT(pipe), WEBRTC);
    gst_element_link_pads(rtpbin, "send_rtcp_src_0", webrtc, "rtcp_sink");
  } else if (g_strcmp0("send_rtp_src_0", GST_OBJECT_NAME(pad)) == 0) {
    GstElement *webrtc = g_object_get_data(G_OBJECT(pipe), WEBRTC);
    gst_element_link_pads(rtpbin, "send_rtp_src_0", webrtc, "sink");
  } else if (g_str_has_suffix(GST_OBJECT_NAME(pad), "_100")) {
    GstElement *depay, *deco, *typefind;
    gint timeout;

    depay = gst_element_factory_make("rtpvp8depay", NULL);
    deco = gst_element_factory_make("vp8dec", NULL);
    typefind = gst_element_factory_make("typefind", NULL);

    g_assert(depay != NULL);
    g_assert(deco != NULL);
    g_assert(typefind != NULL);

    g_object_set(deco, "post-processing", TRUE, NULL);

    g_signal_connect(typefind, "have-type", G_CALLBACK(have_type), pipe);

    gst_element_set_state(depay, GST_STATE_PLAYING);
    gst_element_set_state(deco, GST_STATE_PLAYING);
    gst_element_set_state(typefind, GST_STATE_PLAYING);
    gst_bin_add_many(GST_BIN(pipe), depay, deco, typefind, NULL);

    gst_element_link_pads(rtpbin, GST_OBJECT_NAME(pad), depay, "sink");
    gst_element_link_many(depay, deco, typefind, NULL);

    timeout = g_timeout_add(500, (GSourceFunc) request_key_frame, typefind);
    g_object_set_data_full(G_OBJECT(typefind), "timeout",
                           GINT_TO_POINTER(timeout),
                           (GDestroyNotify) g_source_remove);

  } else {
    GstElement *fakesink = gst_element_factory_make("fakesink", NULL);
    gst_element_set_state(fakesink, GST_STATE_PLAYING);
    gst_bin_add(GST_BIN(pipe), fakesink);

    gst_element_link_pads(rtpbin, GST_OBJECT_NAME(pad), fakesink, "sink");
  }
}

static void
webrtc_pad_added (GstElement *webrtc, GstPad *pad, GstElement *pipe)
{
  GstElement *rtpbin;

  if (gst_pad_get_direction(pad) != GST_PAD_SRC)
    return;

  GST_INFO_OBJECT(pipe, "webrtc pad added: %s", GST_OBJECT_NAME(pad));

  rtpbin = g_object_get_data(G_OBJECT(pipe), RTP);

  if (g_strcmp0("rtcp_src", GST_OBJECT_NAME(pad)) == 0) {
    GstPad *rtcp_pad;
    GstPad *rtp_pad;

    /* rtpbin Needs to create first the rtp pad*/
    rtp_pad = gst_element_get_static_pad(rtpbin, "recv_rtp_sink_0");
    if (rtp_pad == NULL)
      rtp_pad = gst_element_get_request_pad(rtpbin, "recv_rtp_sink_0");
    g_object_unref(rtp_pad);

    gst_element_link_pads(webrtc, GST_OBJECT_NAME(pad), rtpbin, "recv_rtcp_sink_0");
    // Create rtcp pad, it will be linked on pad_added callback
    rtcp_pad = gst_element_get_static_pad(rtpbin, "send_rtcp_src_0");
    if (rtcp_pad == NULL)
      rtcp_pad = gst_element_get_request_pad(rtpbin, "send_rtcp_src_0");
    g_object_unref(rtcp_pad);

  } else {
    gst_element_link_pads(webrtc, GST_OBJECT_NAME(pad), rtpbin, "recv_rtp_sink_0");
  }
}

static GstElement *
create_pipeline (SoupServer *server, SoupMessage *msg, gint64 id)
{
  gint64 *id_pointer;
  GstBus *bus;
  GstElement *pipe = gst_pipeline_new(NULL);
  GstStructure *sdes;

  GstElement *webrtc = gst_element_factory_make("webrtcbin", NULL);
  GstElement *rtpbin = gst_element_factory_make("rtpbin", NULL);

  g_assert(pipe != NULL);
  g_assert(webrtc != NULL);

  id_pointer = g_malloc(sizeof(guint64));
  *id_pointer = id;
  g_object_set_data_full(G_OBJECT(pipe), WEBRTC, g_object_ref(webrtc), g_object_unref);
  g_object_set_data_full(G_OBJECT(pipe), RTP, g_object_ref(rtpbin), g_object_unref);
  g_object_set_data_full(G_OBJECT(pipe), ID, id_pointer, g_free);

  bus = gst_pipeline_get_bus(GST_PIPELINE(pipe));
  gst_bus_add_watch(bus, gst_bus_async_signal_func, NULL);
  g_signal_connect(bus, "message", G_CALLBACK(bus_msg), pipe);
  g_object_unref(bus);

  g_object_set_data_full(G_OBJECT(webrtc), SERVER, g_object_ref(server),
                         g_object_unref);
  g_object_set_data_full(G_OBJECT(webrtc), MESSAGE, g_object_ref(msg),
                         g_object_unref);

  g_signal_connect(webrtc, "local-candidates-gathered",
                   G_CALLBACK (local_candidates_gathered), pipe);
  g_signal_connect(rtpbin, "request-pt-map",
                   G_CALLBACK (rtpbin_request_pt_map), pipe);
  g_signal_connect(rtpbin, "pad-added",
                   G_CALLBACK (rtpbin_pad_added), pipe);
  g_signal_connect(webrtc, "pad-added",
                   G_CALLBACK (webrtc_pad_added), pipe);

  sdes = gst_structure_new("application/x-rtp-source-sdes",
                           "cname", G_TYPE_STRING, "wn88gVLY7eZbEHLB",
                           "tool", G_TYPE_STRING, "Kurento",
                           NULL);
  g_object_set(rtpbin,
               "sdes", sdes,
               "do-lost", TRUE,
               "latency", 100,
               "drop-on-latency", FALSE,
               NULL);
  gst_structure_free(sdes);

  g_object_set(webrtc, "stun-server", "77.72.174.167", NULL);

  gst_bin_add_many(GST_BIN(pipe), webrtc, rtpbin, NULL);

  return pipe;
}

static gboolean
configure_pipeline(GstElement * pipe, const gchar *sdp)
{
  GstElement *webrtc = g_object_get_data(G_OBJECT(pipe), WEBRTC);
  gchar **lines, **idx;
  gboolean ret;
  GstStructure * credentials = gst_structure_new_empty("credentials");
  GstStructure * candidates = gst_structure_new_empty("candidates");

  lines = g_strsplit_set(sdp, "\r\n", -1);

  for (idx = lines; *idx != NULL; idx++) {
    gchar *line = *idx;

    if (g_str_has_prefix (line, CANDIDATE_PREFIX)) {
      GstStructure * candidate;
      gint n_candidates;
      gchar * name;

      candidate = gst_structure_new ("candidate",
                                     "sdpline", G_TYPE_STRING, line, NULL);

      n_candidates = gst_structure_n_fields (candidates);
      name = g_strdup_printf("candidate_%d", n_candidates);

      gst_structure_set(candidates, name, GST_TYPE_STRUCTURE, candidate, NULL);

      g_free (name);
      gst_structure_free (candidate);
    } else if (g_str_has_prefix (line, USER_PREFIX)) {
      gchar * user = line + sizeof(USER_PREFIX) - 1;

      gst_structure_set(credentials, "user", G_TYPE_STRING, user, NULL);
    } else if (g_str_has_prefix (line, PWD_PREFIX)) {
      gchar * passwd = line + sizeof(PWD_PREFIX) - 1;

      gst_structure_set(credentials, "passwd", G_TYPE_STRING, passwd, NULL);
    } else if (g_str_has_prefix(line, CRYPTO_PREFIX)) {
      gchar *crypto_data = line + sizeof(CRYPTO_PREFIX) - 1;
      gchar *token;
      gchar **tokens;
      int i;

      tokens = g_strsplit_set(crypto_data, " :", 5);

      for (token = tokens[0], i = 0; token != NULL;
           token = tokens[++i]) {
        if (token[0] == '\0') {
          GST_ERROR("Invalid crypto line\n");
          g_main_loop_quit(loop);
          goto free_crypto;
        }
      }

      if (i < 4) {
        GST_ERROR("Invalid crypto line\n");
        g_main_loop_quit(loop);
        goto free_crypto;
      }

      if (g_strcmp0(tokens[1], DEFAULT_CRYTPO_TYPE) != 0) {
        GST_ERROR("Invalid crypto line\n");
        g_main_loop_quit(loop);
        goto free_crypto;
      }

      g_object_set (webrtc, "recv-key", tokens[3], NULL);

free_crypto:
      g_strfreev(tokens);

    }
  }
  g_strfreev(lines);

  g_signal_emit_by_name (webrtc, "set-remote-descriptor", candidates,
                         credentials, &ret);

  gst_structure_free (candidates);
  gst_structure_free (credentials);

  GST_DEBUG("configuration: %d", ret);
  return ret;
}

static void
server_callback (SoupServer *server, SoupMessage *msg, const char *path,
                 GHashTable *query, SoupClientContext *client,
                 gpointer user_data)
{
  const char *mime_type = "text/html";
  const char *cookie_str;
  GFile *file;
  GInputStream *file_is;
  gsize readed;
  gchar buffer[BUFFER_LEN];
  SoupCookie *cookie = NULL;
  gint64 id, *id_ptr;
  GstElement *pipe = NULL;

  GST_INFO("Request: %s", path);

  if (msg->method != SOUP_METHOD_GET) {
    soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
    soup_message_set_response (msg, mime_type, SOUP_MEMORY_STATIC, "", 0);
    GST_INFO("Not implemented");
    return;
  }

  if (g_strcmp0(path, "/") != 0) {
    soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
    soup_message_set_response (msg, mime_type, SOUP_MEMORY_STATIC, "", 0);
    GST_INFO("Not found");
    return;
  }


  cookie_str = soup_message_headers_get_list(msg->request_headers, "Cookie");
  if (cookie_str != NULL) {
    gchar ** tokens = g_strsplit(cookie_str, ";", 0);
    gchar **token;
    for (token = tokens; *token != NULL; token++) {
      cookie = soup_cookie_parse (*token, NULL);

      if (cookie != NULL) {
        if (g_strcmp0(cookie->name, "id") == 0) {
          id = g_ascii_strtoll(cookie->value, NULL, 0);
          if (id != G_GINT64_CONSTANT(0)) {
            GST_DEBUG("Found id: %li", id);
            pipe = g_hash_table_lookup(cookies, &id);
            break;
          }
        }
        soup_cookie_free (cookie);
        cookie = NULL;
      }
    }
    g_strfreev(tokens);
  }

  if (cookie == NULL) {
    gchar *id_str;
    const gchar *host;
    id = g_rand_double_range(rand, (double) G_MININT64, (double) G_MAXINT64);
    id_str = g_strdup_printf("%" G_GINT64_FORMAT, id);
    host = soup_message_headers_get_one(msg->request_headers, "Host");
    if (host == NULL)
      host = "localhost";

    cookie = soup_cookie_new("id", id_str, host, path, -1);
    g_free(id_str);
  }

  if (query != NULL) {
    gchar * sdp = g_hash_table_lookup(query, "sdp");
    if (sdp != NULL && pipe != NULL) {
      if (configure_pipeline(pipe, sdp)) {
        soup_message_set_status (msg, SOUP_STATUS_OK);
      } else {
        soup_message_set_status (msg, SOUP_STATUS_NOT_ACCEPTABLE);
      }
      soup_message_set_response (msg, mime_type, SOUP_MEMORY_STATIC, "", 0);
      return;
    }
  }

  g_hash_table_remove(cookies, &id);
  pipe = NULL;

  char *header = soup_cookie_to_cookie_header(cookie);
  if (header != NULL) {
    soup_message_headers_append(msg->response_headers, "Set-Cookie", header);
    g_free(header);
  } else {
    GST_WARNING("Null cookie");
  }
  soup_cookie_free (cookie);

  pipe = create_pipeline (server, msg, id);
  id_ptr = g_malloc(sizeof(gint64));
  *id_ptr = id;
  g_hash_table_insert(cookies, id_ptr, pipe);

  soup_message_set_status (msg, SOUP_STATUS_OK);
  soup_message_set_response (msg, mime_type, SOUP_MEMORY_STATIC, "", 0);

  file = g_file_new_for_path ("index.html");
  g_assert(file != NULL);
  file_is = (GInputStream *) g_file_read (file, NULL, NULL);
  g_assert(file_is != NULL);

  do  {
    readed = g_input_stream_read(file_is, buffer, BUFFER_LEN, NULL, NULL);
    if (readed > 0)
      soup_message_body_append (msg->response_body, SOUP_MEMORY_COPY, buffer,
                                 readed);
  } while(readed > 0);

  g_input_stream_close (file_is, NULL, NULL);

  soup_server_pause_message (server, msg);

  gst_element_set_state(pipe, GST_STATE_PLAYING);
}

static void
destroy_pipe (gpointer data)
{
  GstElement *pipe = GST_ELEMENT(data);

  GST_DEBUG_OBJECT(pipe, "Destroy pipe");
  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipe), GST_DEBUG_GRAPH_SHOW_ALL,
                                    GST_OBJECT_NAME(pipe));
  gst_element_set_state(pipe, GST_STATE_NULL);
  g_object_unref(pipe);
}

static void
sigHandler(int signal)
{
  g_main_loop_quit(loop);
}

gint
main (gint argc, gchar **argv)
{
  SoupServer *server;

  gst_init(&argc, &argv);

  GST_DEBUG_CATEGORY_INIT(GST_CAT_DEFAULT, "webrtc_http_server", 0,
                          "webrtc_http_server");

  loop = g_main_loop_new(NULL, TRUE);
  cookies = g_hash_table_new_full(g_int64_hash, g_int64_equal, g_free, destroy_pipe);
  rand = g_rand_new();

  signal(SIGINT, sigHandler);
  signal(SIGKILL, sigHandler);
  signal(SIGTERM, sigHandler);

  server = soup_server_new(SOUP_SERVER_PORT, PORT, NULL);

  soup_server_add_handler(server, "/", server_callback, NULL, NULL);
  soup_server_run_async(server);

  if (g_main_loop_is_running(loop))
    g_main_loop_run(loop);

  g_hash_table_destroy(cookies);
  g_rand_free(rand);

  return 0;
}
